// Package user_service_v1
// @Author zhongxc
// @Date 2024/5/9 13:56:00
// @Desc
package user_service_v1

import (
	"context"
	"encoding/json"
	"github.com/jinzhu/copier"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"log"
	"project-user/pkg/cache"
	"project-user/pkg/dao"
	"project-user/pkg/model"
	"project-user/pkg/repo"
	"project-user/pkg/svc"
	"strconv"
	"time"
)

type UserService struct {
	UnimplementedUserServiceServer
	svcCtx   *svc.ServiceContext
	userRepo repo.UserRepo
	cache    repo.Cache
}

func NewUserService(svcCtx *svc.ServiceContext) *UserService {
	return &UserService{
		svcCtx:   svcCtx,
		userRepo: dao.NewUserDao(svcCtx.Db),
		cache:    cache.Rc,
	}
}

// queryWithPassThrough 查询用户，解决缓存穿透的问题
func (u *UserService) queryWithPassThrough(ctx context.Context, userId int64) (*model.User, error) {
	userKey := "user:info:" + strconv.FormatInt(userId, 10)
	ctx2, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancelFunc()
	// 从redis查询用户缓存
	userJson, err := u.cache.Get(ctx2, userKey)
	if err != nil {
		return nil, status.Error(codes.NotFound, "redis error")
	}
	// 判断是否存在
	if userJson != "" && userJson != "NULL" {
		// 存在就直接返回
		user := model.User{}
		err := json.Unmarshal([]byte(userJson), &user)
		if err != nil {
			return nil, status.Error(codes.NotFound, "json unmarshal error")
		}
		return &user, nil
	}
	// 判断命中的是否是空值
	if userJson == "NULL" {
		return nil, status.Error(codes.NotFound, "user not found NULL")
	}

	// 不存在，根据ID查询数据库
	user, err := u.userRepo.GetUserById(context.Background(), userId)
	if err != nil {
		return nil, status.Error(codes.ResourceExhausted, err.Error())
	}
	// 数据库查询不存在，返回错误
	if user == nil {
		// 将空值写入redis
		err = u.cache.Put(ctx, userKey, "NULL", 600*time.Second)
		if err != nil {
			return nil, status.Error(codes.Internal, err.Error())
		}
		return nil, status.Error(codes.NotFound, "user not found")
	}

	marshal, err := json.Marshal(user)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	// 将用户信息写入redis
	err = u.cache.Put(ctx, userKey, string(marshal), 600*time.Second)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	return user, nil
}

// queryWithMutex 查询用户，互斥锁解决缓存击穿
func (u *UserService) queryWithMutex(ctx context.Context, userId int64) (*model.User, error) {
	userKey := "user:info:" + strconv.FormatInt(userId, 10)
	// ctx2, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second)
	// defer cancelFunc()
	// 从redis查询用户缓存
	userJson, err := u.cache.Get(ctx, userKey)
	if err != nil {
		return nil, status.Error(codes.NotFound, "redis error")
	}
	// 判断是否存在
	if userJson != "" && userJson != "NULL" {
		// 存在就直接返回
		user := model.User{}
		err := json.Unmarshal([]byte(userJson), &user)
		if err != nil {
			return nil, status.Error(codes.NotFound, "json unmarshal error")
		}
		return &user, nil
	}
	// 判断命中的是否是空值
	if userJson == "NULL" {
		return nil, status.Error(codes.NotFound, "user not found NULL")
	}

	// 实现缓存重建
	//  获取互斥锁
	lockKey := "lock:user:info:" + strconv.FormatInt(userId, 10)
	isLock := u.tryLock(ctx, lockKey)
	//  判断是否获取成功
	if !isLock {
		//  失败，则休眠重试
		time.Sleep(50 * time.Millisecond)
		return u.queryWithMutex(ctx, userId)
	}
	// 释放互斥锁
	defer func() {
		u.unlock(ctx, lockKey)
	}()

	// TODO 注意：获取锁成功应该再次检测redis缓存是否存在，做DoubleCheck，如果存在，则无需重建缓存
	// 从redis查询用户缓存
	userJson, err = u.cache.Get(ctx, userKey)
	if err != nil {
		return nil, status.Error(codes.NotFound, "redis error")
	}
	// 判断是否存在
	if userJson != "" && userJson != "NULL" {
		// 存在就直接返回
		user := model.User{}
		err := json.Unmarshal([]byte(userJson), &user)
		if err != nil {
			return nil, status.Error(codes.NotFound, "json unmarshal error")
		}
		return &user, nil
	}

	// 不存在，根据ID查询数据库
	user, err := u.userRepo.GetUserById(context.Background(), userId)
	if err != nil {
		return nil, status.Error(codes.ResourceExhausted, err.Error())
	}
	// 数据库查询不存在，返回错误
	if user == nil {
		// 将空值写入redis
		err = u.cache.Put(ctx, userKey, "NULL", 600*time.Second)
		if err != nil {
			return nil, status.Error(codes.Internal, err.Error())
		}
		return nil, status.Error(codes.NotFound, "user not found")
	}

	marshal, err := json.Marshal(user)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	// 将用户信息写入redis
	err = u.cache.Put(ctx, userKey, string(marshal), 600*time.Second)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	return user, nil
}

func (u *UserService) GetUserById(ctx context.Context, req *UserRequest) (*UserResponse, error) {
	// 解决缓存穿透
	// user, err := u.queryWithPassThrough(ctx, req.UserId)
	// if err != nil {
	// 	return nil, err
	// }

	// 互斥锁解决缓存击穿
	user, err := u.queryWithMutex(ctx, req.UserId)
	if err != nil {
		return nil, err
	}

	return &UserResponse{
		UserId:   user.UserId,
		UserName: user.Username,
		Nickname: user.Nickname,
		Email:    user.Email,
		Mobile:   user.Mobile,
	}, nil
}

// tryLock 尝试获取锁
func (u *UserService) tryLock(ctx context.Context, key string) bool {
	result, err := u.cache.PutIfAbsent(ctx, key, "", 600*time.Second)
	if err != nil {
		log.Println("try lock ", err)
		return false
	}
	return result
}

// unlock 释放锁
func (u *UserService) unlock(ctx context.Context, key string) {
	_ = u.cache.Delete(ctx, key)
}

func (u *UserService) GetUserList(ctx context.Context, in *UserListRequest) (*UserListResponse, error) {
	mapList, err := u.userRepo.GetUserList(ctx, in.Page, in.PageSize)
	if err != nil {
		return nil, status.Error(codes.ResourceExhausted, err.Error())
	}

	userList, ok := mapList["list"].([]*model.User)
	if !ok {
		return nil, err
	}

	var userListData []*UserListData
	for _, user := range userList {
		userData := UserListData{}
		_ = copier.Copy(&userData, &user)
		userListData = append(userListData, &userData)
	}

	currentPage, _ := mapList["page"].(int64)
	pageSize, _ := mapList["pageSize"].(int64)
	pageCount, _ := mapList["pageCount"].(int64)
	total, _ := mapList["total"].(int64)
	resp := &UserListResponse{
		CurrentPage: currentPage,
		PageSize:    pageSize,
		PageCount:   pageCount,
		TotalCount:  total,
		Data:        userListData,
	}

	return resp, nil
}
